package io.demo.message.serial;


import com.google.common.io.BaseEncoding;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Objects;

/**
 * Thread-safe Serialization Object
 * <pre>
 *   <strong><em>Constraints:</em></strong>
 *   All instances of {@link SerializedObject} are supposed to be immutable, making the instances
 *   thread-safe.
 * </pre>
 *
 * @param <T> The object Type
 */
public abstract class SerializedObject<T> {

  private final T object;

  private final byte[] byteArray;

  /**
   * Using final String holder to avoid unnecessary problems. Like eager initialization, mutation of
   * the field.
   */
  private final StringHolder utf8String;

  private final StringHolder base64String;

  SerializedObject(T object) {
    if (Objects.isNull(object)) {
      throw new IllegalArgumentException();
    }

    this.object = object;
    beforeSerialize();
    this.byteArray = doSerialization(this.object);
    this.utf8String = LazyStringHolder.utf8(byteArray);
    this.base64String = LazyStringHolder.base64(byteArray);
  }

  /**
   * Job that has to be done before serialization
   */
  protected abstract void beforeSerialize();

  protected abstract byte[] doSerialization(T object);


  /**
   * The attempt to deserialize current object back to original object.
   *
   * @return Object deserialized from {@link #byteArray}. There is a chance that the deserialized
   * object would be different from the original one.
   */
  public T reverse() {
    String message = MessageFormat.format(
        "{0} does not support such operation", getClass());
    throw new UnsupportedOperationException(message);
  }

  public static <E> KryoSerializedObject<E> kryo(E object) {
    return new KryoSerializedObject<>(object);
  }

  public static <E> Hessian2SerializedObject<E> hessian2(E object) {
    return new Hessian2SerializedObject<>(object);
  }

  public static <E extends Serializable> JdkSerializedObject<E> jdk(E object) {
    return new JdkSerializedObject<>(object);
  }

  public static <E> ProtobufSerializedObject<E> protobuf(E object) {
    return new ProtobufSerializedObject<>(object);
  }


  public static <E> JacksonSerializedObject<E> jackson(E object) {
    return new JacksonSerializedObject<>(object);
  }

  public static <E> GsonSerializedObject<E> gson(E object) {
    return new GsonSerializedObject<>(object);
  }

  public static <E> FastjsonSerializedObject<E> fastjson(E object) {
    return new FastjsonSerializedObject<>(object);
  }

  public T getObject() {
    return object;
  }

  protected Class<?> getObjectClass() {
    return object.getClass();
  }

  /**
   * @return A defensive copy of current byteArray, making current instance remains Immutable.
   */
  public byte[] getByteArray() {
    return Arrays.copyOf(byteArray, byteArray.length);
  }

  public int getByteArrayLength() {
    return byteArray.length;
  }

  public String getBase64String() {
    return base64String.get();
  }

  public String getUtf8String() {
    return utf8String.get();
  }

  /**
   * Hold string to avoid unnecessary modification.
   *
   * @author imlijinting
   */
  private interface StringHolder {

    String get();
  }

  private abstract static class LazyStringHolder implements StringHolder {

    private byte[] bytes;

    private String value;

    LazyStringHolder(byte[] bytes) {
      this.bytes = bytes;
    }

    static Base64StringHolder base64(byte[] arr) {
      return new Base64StringHolder(arr);
    }

    static UTF8StringHolder utf8(byte[] arr) {
      return new UTF8StringHolder(arr);
    }

    abstract String initializeString(byte[] byteArray);

    @Override
    public String get() {
      if (value == null) {
        value = initializeString(bytes);
      }
      return value;
    }
  }

  private static class Base64StringHolder extends LazyStringHolder {

    private static final BaseEncoding BASE_ENCODING = BaseEncoding.base64();

    Base64StringHolder(byte[] bytes) {
      super(bytes);
    }

    @Override
    protected String initializeString(byte[] byteArray) {
      return BASE_ENCODING.encode(byteArray);
    }
  }

  private static class UTF8StringHolder extends LazyStringHolder {

    UTF8StringHolder(byte[] bytes) {
      super(bytes);
    }

    @Override
    protected String initializeString(byte[] byteArray) {
      return new String(byteArray, StandardCharsets.UTF_8);
    }
  }
}



